---
id: useSuspenseIndexedDBState
title: useSuspenseIndexedDBState
sidebar_label: useSuspenseIndexedDBState
---

## About

⚠️ **Experimental Hook**: This hook may be removed or significantly changed in any release without notice.

A Suspense-enabled hook for IndexedDB state management with cross-tab synchronization. This hook suspends during initialization and must be wrapped in a Suspense boundary. It provides full TypeScript generic support, structured data storage without JSON serialization limits, error handling, and automatic synchronization across browser tabs using BroadcastChannel API.

[//]: # "Main"

## Examples

#### Basic usage with simple values

```tsx
import React, { Suspense } from "react";
import { useSuspenseIndexedDBState } from "rooks/experimental";

function Counter() {
  const [count, { setItem, deleteItem }] = useSuspenseIndexedDBState(
    'my-counter',
    (currentValue) => typeof currentValue === 'number' ? currentValue : 0
  );
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setItem(count + 1)}>Increment</button>
      <button onClick={deleteItem}>Reset</button>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Counter />
    </Suspense>
  );
}
```

#### Working with Complex Objects and TypeScript

```tsx
import React, { Suspense } from "react";
import { useSuspenseIndexedDBState } from "rooks/experimental";

interface UserProfile {
  id: number;
  name: string;
  email: string;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
  createdAt: Date;
}

function UserProfileManager() {
  const [profile, { setItem, getItem, deleteItem }] = useSuspenseIndexedDBState(
    'user-profile',
    (currentValue): UserProfile => {
      if (currentValue && typeof currentValue === 'object' && 'id' in currentValue) {
        // IndexedDB can store Date objects directly, no JSON parsing needed
        return currentValue as UserProfile;
      }
      return {
        id: 0,
        name: 'Guest',
        email: '',
        preferences: { theme: 'light', notifications: true },
        createdAt: new Date()
      };
    }
  );

  const updateProfile = () => {
    setItem({
      id: 123,
      name: 'John Doe',
      email: 'john@example.com',
      preferences: { theme: 'dark', notifications: false },
      createdAt: new Date() // Date objects work directly with IndexedDB
    });
  };

  const getCurrentProfile = () => {
    const current = getItem();
    console.log('Current profile:', current);
  };

  return (
    <div>
      <h2>User Profile</h2>
      <p>Name: {profile.name}</p>
      <p>Email: {profile.email}</p>
      <p>Theme: {profile.preferences.theme}</p>
      <p>Created: {profile.createdAt.toLocaleDateString()}</p>
      
      <button onClick={updateProfile}>Update Profile</button>
      <button onClick={getCurrentProfile}>Log Current Profile</button>
      <button onClick={deleteItem}>Reset Profile</button>
    </div>
  );
}
```

#### Custom Database Configuration

```tsx
import React, { Suspense } from "react";
import { useSuspenseIndexedDBState } from "rooks/experimental";

function CustomDBExample() {
  const [data, { setItem }] = useSuspenseIndexedDBState(
    'my-data',
    (currentValue) => currentValue || { items: [] },
    {
      dbName: 'my-app-db',
      storeName: 'app-data',
      version: 2
    }
  );

  return (
    <div>
      <p>Items: {data.items.length}</p>
      <button onClick={() => setItem({
        items: [...data.items, `Item ${data.items.length + 1}`]
      })}>
        Add Item
      </button>
    </div>
  );
}
```

#### Storing Binary Data and Complex Structures

```tsx
import React, { Suspense } from "react";
import { useSuspenseIndexedDBState } from "rooks/experimental";

interface AppData {
  settings: Map<string, any>; // Maps work with IndexedDB
  files: File[]; // File objects work with IndexedDB
  metadata: {
    version: string;
    lastSync: Date;
    tags: Set<string>; // Sets work with IndexedDB
  };
}

function ComplexDataExample() {
  const [appData, { setItem }] = useSuspenseIndexedDBState(
    'complex-app-data',
    (currentValue): AppData => {
      if (currentValue) {
        return currentValue as AppData;
      }
      
      const settings = new Map();
      settings.set('theme', 'light');
      settings.set('autoSave', true);
      
      return {
        settings,
        files: [],
        metadata: {
          version: '1.0.0',
          lastSync: new Date(),
          tags: new Set(['important', 'project'])
        }
      };
    }
  );

  const addFile = (file: File) => {
    setItem({
      ...appData,
      files: [...appData.files, file],
      metadata: {
        ...appData.metadata,
        lastSync: new Date()
      }
    });
  };

  return (
    <div>
      <h2>Complex Data Storage</h2>
      <p>Settings: {appData.settings.size} items</p>
      <p>Files: {appData.files.length}</p>
      <p>Tags: {Array.from(appData.metadata.tags).join(', ')}</p>
      <p>Last Sync: {appData.metadata.lastSync.toLocaleString()}</p>
      
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) addFile(file);
        }}
      />
    </div>
  );
}
```

## Cross-tab Synchronization

The hook automatically synchronizes state changes across browser tabs using the BroadcastChannel API:

```tsx
import React, { Suspense } from "react";
import { useSuspenseIndexedDBState } from "rooks/experimental";

function SyncDemo() {
  const [message, { setItem, deleteItem }] = useSuspenseIndexedDBState(
    'sync-message',
    (currentValue) => currentValue || 'Hello World'
  );
  
  const [lastUpdated, { setItem: setLastUpdated }] = useSuspenseIndexedDBState(
    'last-updated',
    (currentValue) => currentValue || new Date()
  );

  const updateMessage = (newMessage: string) => {
    setItem(newMessage);
    setLastUpdated(new Date());
  };

  const clearAll = () => {
    deleteItem();
    setLastUpdated(new Date());
  };

  return (
    <div>
      <h2>Cross-Tab Sync Demo</h2>
      <p>Open this page in multiple tabs to see real-time synchronization!</p>
      
      <div>
        <strong>Current Message:</strong> {message}
      </div>
      <div>
        <strong>Last Updated:</strong> {lastUpdated.toLocaleTimeString()}
      </div>
      
      <input
        value={message}
        onChange={(e) => updateMessage(e.target.value)}
        placeholder="Type a message..."
      />
      
      <button onClick={() => updateMessage("Updated from tab!")}>
        Update Message
      </button>
      <button onClick={clearAll}>
        Clear Message
      </button>
      
      <p>
        <em>Try changing the message in one tab and watch it update in others!</em>
      </p>
    </div>
  );
}
```

## Arguments

| Argument     | Type                              | Description                                                          | Required |
|-------------|-----------------------------------|----------------------------------------------------------------------|----------|
| key         | `string`                          | The key to use within the IndexedDB object store                   | Yes      |
| initializer | `(currentValue: unknown) => T`        | Function that transforms the raw IndexedDB value into typed state  | Yes      |
| config      | `IndexedDBConfig`                 | Optional configuration object                                        | No       |

### IndexedDBConfig

| Property   | Type     | Default     | Description                           |
|-----------|----------|-------------|---------------------------------------|
| dbName    | `string` | "rooks-db"  | The IndexedDB database name          |
| storeName | `string` | "state"     | The object store name within the DB  |
| version   | `number` | 1           | The database version                  |

## Return Value

Returns a tuple containing:

1. **`currentValue: T`** - The current state value of type T
2. **`controls: object`** - An object containing:
   - `getItem(): T` - Get the current value from state
   - `setItem(value: T): Promise<void>` - Set a new value (async)
   - `deleteItem(): Promise<void>` - Delete the item and reset to initial value (async)

## Type Safety

The hook is fully type-safe and supports TypeScript generics:

```tsx
// The hook infers the type from your initializer function
const [user, controls] = useSuspenseIndexedDBState(
  'user',
  (value): UserProfile => value || defaultUser
);
// `user` is of type UserProfile
// `controls.setItem` expects UserProfile
// `controls.getItem()` returns UserProfile

// You can also be explicit about the type
const [data, { setItem }] = useSuspenseIndexedDBState<MyDataType>(
  'my-data',
  (value) => value || getDefaultData()
);
```

## Error Handling

The hook handles various error scenarios gracefully:

- **IndexedDB not supported**: Falls back to in-memory state
- **Database opening errors**: Throws errors that can be caught by error boundaries
- **Transaction errors**: Logs errors and maintains existing state
- **Initializer errors**: Throws errors that can be caught by error boundaries

```tsx
import React, { Suspense } from "react";
import { useSuspenseIndexedDBState } from "rooks/experimental";

function ErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        {children}
      </Suspense>
    </ErrorBoundary>
  );
}
```

## IndexedDB vs localStorage Comparison

| Feature                    | useSuspenseIndexedDBState | useSuspenseLocalStorageState |
|---------------------------|---------------------------|------------------------------|
| Data Types                | Native objects, binary data | JSON serializable only      |
| Storage Limit             | ~1GB+ (browser dependent) | ~5-10MB                     |
| Performance               | Better for large data     | Better for small data       |
| Cross-tab Sync            | BroadcastChannel API       | Storage events              |
| Browser Support           | Modern browsers            | All browsers                |
| Async Operations          | Yes (Promises)             | No (synchronous)            |
| Complex Objects           | Native support             | JSON serialization required |

## Browser Support

- **IndexedDB**: Supported in all modern browsers (IE 10+)
- **BroadcastChannel**: Supported in modern browsers (Chrome 54+, Firefox 38+, Safari 15.4+)
- **Fallback**: When BroadcastChannel is not available, cross-tab sync is disabled but the hook still works

## Import

```tsx
import { useSuspenseIndexedDBState } from "rooks/experimental";
```